package cn.apthink.schedule;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

import cn.apthink.calendar.Department;
import cn.apthink.calendar.DepartmentCalendar;
import cn.apthink.calendar.DepartmentCalendarHelper;
import cn.apthink.calendar.DepartmentWorker;
import cn.apthink.calendar.Holiday;
import cn.apthink.calendar.SpecialWorkDay;
import cn.apthink.calendar.WeekTemplateDetail;
import cn.apthink.calendar.WorkCalendar;
import cn.apthink.utility.Util;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
public class Line {
	private String id;
	private Integer worker;
	private List<Plan> plans;
	private DepartmentCalendar calendar;
	private EfficiencyHelper efficiencyHelper;
	// 新增计划时有效开始日期
	private LocalDate validStartDate;
	// 是否支持自动消除空隙计划
	private boolean enableCompact;

	public Line(String id, Integer worker) {
		this.id = id;
		this.worker = worker;
	}

	@Override
	public String toString() {
		return "[" + id + "," + worker + "," + plans + "]";
	}

	// 产线上计划重新排序
	private void _sort() {
		plans.sort((a, b) -> {
			return a.getStartTime().compareTo(b.getStartTime());
		});
	}

	// 已排计划指定开始时间后重新计算排产
	private void _refreshPlan(Plan plan, LocalDateTime startTime) {
		LocalDateTime start = calendar.getValidWorkTime(startTime, true);
		plan.setStartTime(start);
		plan.setEndTime(calendar.calcEndTimeByLoadding(start, plan.loaddingByEfficiency()));
		plan.setDuration(calendar.duration(plan.getStartTime(), plan.getEndTime()));
	}

	private void _setEfficiency(Plan plan) {
		if (efficiencyHelper.isEfficiencyLevel()) {
			plan.getProductAmount().forEach((k, v) -> {
				double e = efficiencyHelper.getEfficiency(id, k, v);
				plan.getDetailByProduct(k).forEach(x -> {
					x.setEfficiency(e);
				});
			});
		} else {
			plan.getDetails().forEach(x -> {
				x.setEfficiency(efficiencyHelper.getEfficiency(id, x.getProductId(), null));
			});
		}
	}

//	/**
//	 * 返回计划的时长（考虑产线人数和效率）
//	 * 
//	 * @param plan
//	 * @return
//	 */
//	private double _calcValidDuration(Plan plan) {
//		_setEfficiency(plan);
//		if (Util.isEmpty(plan.getManualworker()))
//			return Math.round(plan.loaddingByEfficiency() / worker);
//		else
//			return Math.round(plan.loaddingByEfficiency() / plan.getManualworker());
//	}

	/**
	 * 返回有效开始时间(考虑系统排产有效时间)，最后一条计划的结束时间+1秒
	 * 
	 * @return
	 */
	public LocalDateTime getValidStartTime() {
		if (Util.isEmpty(plans)) {
			LocalDate st;
			if (Util.isEmpty(validStartDate))
				st = LocalDate.now();
			else
				st = validStartDate;
			return calendar.getValidWorkDay(st).getStartTime();
		} else {
			LocalDate st;
			LocalDateTime et = calendar.getValidWorkTime(plans.get(plans.size() - 1).getEndTime().plusSeconds(1));
			if (Util.isEmpty(validStartDate))
				st = LocalDate.now();
			else
				st = validStartDate;
			return et.isAfter(st.atStartOfDay()) ? et : calendar.getValidWorkDay(st).getStartTime();
		}
	}

	/**
	 * 返回有效开始时间(不考虑系统排产有效时间，插入时间点有计划时计划结束时间+1秒)
	 * 
	 * @return
	 */
	public LocalDateTime getValidStartTime(LocalDateTime startTime) {
		if (!Util.isEmpty(plans)) {
			for (int i = 0, len = plans.size(); i < len; i++) {
				if (!plans.get(i).getStartTime().isAfter(startTime) && !plans.get(i).getEndTime().isBefore(startTime)) {
					LocalDateTime st = plans.get(i).getEndTime().plusSeconds(1);
					// David: 避免后续计划有相同开始时间，计划排序不正确
					if (i < len - 1 && plans.get(i + 1).getStartTime().equals(st)) {
						plans.get(i + 1).setStartTime(st.plusSeconds(1));
					}
					return plans.get(i).getEndTime().plusSeconds(1);
				}
			}
		}
//		if (!Util.isEmpty(validStartDate) && validStartDate.atStartOfDay().isAfter(startTime)) {
//			return calendar.getValidWorkDay(validStartDate).getStartTime();
//		} else {
		return calendar.getValidWorkTime(startTime);
//		}
	}

	/**
	 * 返回超出空闲时长的有效开始时间
	 * 
	 * @return
	 */
	public LocalDateTime getValidStartTime(double loadding) {
		if (Util.isEmpty(plans)) {
			return getValidStartTime();
		} else {
			LocalDateTime st = Util.isEmpty(validStartDate) ? LocalDate.now().atStartOfDay()
					: validStartDate.atStartOfDay();
			for (int i = 0, len = plans.size(); i < len - 1; i++) {
				double d = calendar.capacity(plans.get(i).getEndTime().plusSeconds(1), plans.get(i + 1).getStartTime());
				if (st.isBefore(plans.get(i).getStartTime()) && d >= loadding) {
					return plans.get(i).getEndTime().plusSeconds(1);
				}
			}
			return plans.get(plans.size() - 1).getEndTime().plusSeconds(1);
		}
	}

	public void setPlans(List<Plan> plans) {
		this.plans = plans;
		_sort();
	}

	public void removeUnlockPlans() {
		if (Util.isEmpty(plans))
			return;
		for (int i = plans.size() - 1; i >= 0; i--) {
			if (plans.get(i).isLocked()) {
				break;
			}
			plans.remove(i);
		}
	}

	public List<Plan> getUnlockPlans(LocalDate startDate) {
		if (Util.isEmpty(plans))
			return null;
		List<Plan> result = new ArrayList<Plan>();
		LocalDateTime st = startDate == null ? LocalDate.now().atStartOfDay() : startDate.atStartOfDay();
		for (int i = plans.size() - 1; i >= 0; i--) {
			Plan p = plans.get(i);
			if (!st.isBefore(p.getStartTime()) || p.isLocked()) {
				break;
			}
			result.add(p);
		}
		return result;
	}

	public void refresh(LocalDateTime startTime, boolean forceCompact) {
		boolean currentCompact = enableCompact;
		enableCompact = forceCompact;
		refresh(startTime);
		enableCompact = currentCompact;
	}

	// 产线上从指定时间开始进行重新刷新，不会消除空隙（如遇到锁定计划，自动跳过）
	public void refresh(LocalDateTime startTime) {
		if (Util.isEmpty(plans))
			return;
//		LocalDateTime st = enableCompact ? plans.get(0).getStartTime() : LocalDateTime.MIN;
		LocalDateTime st = plans.get(0).getStartTime();
		for (int i = 0, len = plans.size(); i < len; i++) {
			Plan p = plans.get(i);
			if (p.isLocked() || p.getEndTime().isBefore(startTime)) {
				if (enableCompact) {
					st = p.getEndTime().plusSeconds(1);
				}
				continue;
			}
			if (!enableCompact && p.getStartTime().isAfter(st)) {
				st = p.getStartTime();
			}
			_refreshPlan(p, st);
			st = p.getEndTime().plusSeconds(1);
		}
	}

	public PlanKPI preAddPlan(Plan plan, int checkDays) {
		_setEfficiency(plan);
		PlanKPI pk = new PlanKPI();
		pk.setDepartmentId(this.id);
		pk.setPlanId(plan.getId());
		pk.setEarliestStartDate(plan.earliestStartDate());
		pk.setLatestEndDate(plan.latestEndDate());
		LocalDateTime st = getValidStartTime();
		LocalDateTime et = calendar.calcEndTimeByLoadding(st, plan.loaddingByEfficiency());
		pk.setStartDate(st.toLocalDate());
		pk.setEndDate(et.toLocalDate());
		pk.setEfficiency(plan.maxEfficiency());
		pk.setContinueProduct(false);
		// 指定天数内是否存在相同的款号或产品Id
		if (checkDays>0 && !Util.isEmpty(plans) && (!Util.isEmpty(plan.getStyleId()) || !Util.isEmpty(plan.getProductId()))) {
			LocalDateTime t = st.toLocalDate().minusDays(checkDays).atStartOfDay();
			if (plans.stream().filter(x -> {
				return x.getEndTime().isAfter(t) && ((!Util.isEmpty(plan.getStyleId())
						&& plan.getStyleId().equals(x.getStyleId()))
						|| (!Util.isEmpty(plan.getProductId()) && plan.getProductId().equals(x.getProductId())));
			}).findAny().isPresent()) {
				pk.setContinueProduct(true);
			}
		}
		return pk;
	}

	public void addPlan(Plan plan) {
		if (Util.isEmpty(plans)) {
			plans = new ArrayList<Plan>();
		}
		_setEfficiency(plan);
		plan.setDepartmentId(id);
		plan.setStartTime(getValidStartTime());
		plan.setEndTime(calendar.calcEndTimeByLoadding(plan.getStartTime(), plan.loaddingByEfficiency()));
		plan.setDuration(calendar.duration(plan.getStartTime(), plan.getEndTime()));
		plan.setWorker(worker);
		plans.add(plan);
	}

	public void insertPlan(Plan plan, LocalDateTime startTime) {
		if (Util.isEmpty(plans)) {
			plans = new ArrayList<Plan>();
		}
		_setEfficiency(plan);
		plan.setDepartmentId(id);
		plan.setStartTime(getValidStartTime(startTime));
		plan.setEndTime(calendar.calcEndTimeByLoadding(plan.getStartTime(), plan.loaddingByEfficiency()));
		plan.setDuration(calendar.duration(plan.getStartTime(), plan.getEndTime()));
		plan.setWorker(worker);
		plans.add(plan);
		_sort();
		refresh(startTime);
	}

	public static Line testData(String id, int worker) {
		Line line = new Line(id, worker);
		DepartmentCalendarHelper dcHelper = DepartmentCalendarHelper.testData();
		line.setCalendar(dcHelper.getDepartmentCalendar(id));
		line.setEfficiencyHelper(EfficiencyHelper.testData());
		line.setPlans(Plan.testDatas(id));
		return line;
	}

}
